﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ITAP.ShprossesBuilder.Modelling.Components;
using ITAP.ShprossesBuilder.Calculations;
using System.Drawing;
using ITAP.Core.Modelling.Primitives;
using ITAP.ShprossesBuilder.Editor;
using ITAP.Core.Calculations;
using ITAP.Core.Modelling;

namespace ITAP.ShprossesBuilder.Modelling.Measurement
{
    public enum DimensionSpreadingType
    {
        Simple,
        ByNearestSide
    }

    public enum DimensionType
    {
        InnerDimension,
        OuterDimension,
        NotDefined
    }

    public class DefaultDimensionProcessor : IDimensionProcessor
    {
        private DimensionEqualityComparer equalityComparer = new DimensionEqualityComparer();
        private DimensionPriorityComparer dimensionComparer = new DimensionPriorityComparer(new Type[] { typeof(PinHolesDimension), typeof(SizeDimension) });
        private List<Type> allowedTypesToCompute;
        private bool enabled = true;

        public bool Enabled
        {
            get { return enabled; }
            set { enabled = value; }
        }

        protected ModelCoordinateConverter coordinateConverter = new ModelCoordinateConverter();
        protected RectangleF modelBoundingBox;
        protected int leftSizesCounter;
        protected int rightSizesCounter;
        protected int downSizesCounter;
        protected int upSizesCounter;
        protected double noizeValue;

        //private List<PinHolesDimension> arcDimensions = new List<PinHolesDimension>();

        protected DimensionType spacerElementDimensionType = DimensionType.InnerDimension;

        public virtual DimensionType SpacerElementDimensionType
        {
            get { return spacerElementDimensionType; }
            set { spacerElementDimensionType = value; }
        }
        #region IDimensionProcessor Members

        protected List<Dimension> dimensions;
        protected DimensionSpreadingType dimensionSpreadingType;
        private DimensionsPreferences dimensionsPreferences;

        public DimensionsPreferences DimensionsPreferences
        {
            get { return dimensionsPreferences; }
            set { dimensionsPreferences = value; }
        }
        public virtual List<Dimension> Dimensions
        {
            get { return dimensions; }
        }

        protected List<Dimension> offsetDimensions;

        public List<Dimension> OffsetDimensions
        {
            get { return offsetDimensions; }
            set { offsetDimensions = value; }
        }

        public DefaultDimensionProcessor(Type[] typesOfElements)
        {
            dimensions = new List<Dimension>();
            offsetDimensions = new List<Dimension>();
            this.allowedTypesToCompute = new List<Type>(typesOfElements);
        }

        public virtual void Compute(ModelCell modelCell, double noizeValue, DimensionSpreadingType dimensionSpreadingType, DimensionsPreferences preferences)
        {
            this.dimensionsPreferences = preferences;
            offsetDimensions.Clear();
            dimensions.Clear();

            if (!enabled)
                return;

            this.dimensionSpreadingType = dimensionSpreadingType;
            leftSizesCounter = 0;
            upSizesCounter = 0;
            rightSizesCounter = 0;
            downSizesCounter = 0;
            modelBoundingBox = modelCell.Model.BoundingBox;
            this.noizeValue = noizeValue;
            foreach (IComponent innerComponent in modelCell.AllInnerComponents)
            {
                if (this.allowedTypesToCompute.Contains(innerComponent.GetType()))
                    innerComponent.AcceptVisitor(this);
            }

            this.DeleteIdenticalDimensions();

            this.dimensions.Sort(0, this.dimensions.Count, dimensionComparer);

            //double distance = ModelCalculations.GetDistance(this.dimensions[0].Primitive as Line, modelBoundingBox);

            int dimensionCounter = 0;
            while (dimensionCounter != this.dimensions.Count)
            {
                if (this.RaiseLevelForCollideDimensions(this.dimensions[dimensionCounter]))
                    dimensionCounter = 0;
                else
                    dimensionCounter++;
            }
        }

        //public RectangleF FindArcDimensionsBoundingBox(ModelCell modelCell)
        //{
        //    Model model = modelCell.Model;
        //    RectangleF result = model.BoundingBox;
        //    foreach (IComponent innerComponent in model.AllInnerComponents)
        //    {
        //        if (innerComponent is Spacer)
        //        {
        //            Spacer spacer = innerComponent as Spacer;
        //            foreach (IComponent connectedComponent in spacer.ConnectedComponents)
        //            {
        //                if (connectedComponent is SpacerConnector)
        //                {
        //                    SpacerConnector spacerConnector = connectedComponent as SpacerConnector;
        //                    Dimension dimension = DimensionFactory.CreatePinHoleDimension(spacerConnector, spacerConnector.ConnectedSpacerElement, DimensionType.InnerDimension, true);
        //                    if (dimension.Primitive.Height != 0)
        //                        result = this.GetUnionRectangle(dimension.Primitive.BoundingBox, result);
        //                }
        //            }
        //        }
        //    }
        //    return result;
        //}

        protected virtual bool RaiseLevelForCollideDimensions(Dimension dimension)
        {
            bool result = false;
            foreach (Dimension other in this.dimensions)
                if (dimension != other)
                {
                    if (dimension.IsCollide(other))
                    {
                        dimension.Level++;
                        result = true;
                    }
                    else if (dimension.IsOverlap(other))
                    {
                        if (dimension.Primitive.Length < other.Primitive.Length)
                        {
                            other.Level++;
                            result = true;
                        }
                        else
                        {
                            dimension.Level++;
                            result = true;
                        }
                    }

                }
            return result;
        }

        protected virtual void DeleteIdenticalDimensions()
        {
            //IEnumerable<Dimension> filter = this.dimensions.Distinct<Dimension>(EqualityComparer<Dimension>.Default);
            IEnumerable<Dimension> filter = this.dimensions.Distinct<Dimension>(equalityComparer);
            this.dimensions = new List<Dimension>(filter);
        }

        public virtual void Compute(ModelCell modelCell, ModelCoordinateConverter coordinateConverter, double noizeValue, DimensionSpreadingType dimensionSpreadingType, DimensionsPreferences preferences)
        {
            this.coordinateConverter = coordinateConverter;
            this.Compute(modelCell, noizeValue, dimensionSpreadingType, preferences);
        }

        #endregion

        #region IModelVisitor Members

        public virtual void Visit(Model model)
        {
            SizeDimension dimension = new SizeDimension(DimensionFactory.GetSideLine(modelBoundingBox, DimensionSide.Left));
            dimension.Side = DimensionSide.Left;
            this.dimensions.Add(dimension);
            dimension = new SizeDimension(DimensionFactory.GetSideLine(modelBoundingBox, DimensionSide.Right));
            dimension.Side = DimensionSide.Right;
            this.dimensions.Add(dimension);
            dimension = new SizeDimension(DimensionFactory.GetSideLine(modelBoundingBox, DimensionSide.Up));
            dimension.Side = DimensionSide.Up;
            this.dimensions.Add(dimension);
            dimension = new SizeDimension(DimensionFactory.GetSideLine(modelBoundingBox, DimensionSide.Down));
            dimension.Side = DimensionSide.Down;
            this.dimensions.Add(dimension);
        }

        public virtual void Visit(Connector connector)
        {
        }

        public virtual void Visit(Shpross shpross)
        {
            Dimension xDimension;
            Dimension yDimension;

            DimensionSide xSide;

            DimensionSide ySide;
            if (dimensionSpreadingType == DimensionSpreadingType.Simple)
            {
                xSide = this.GetAppropriateXSide();
                ySide = this.GetAppropriateYSide();
            }
            else if (dimensionSpreadingType == DimensionSpreadingType.ByNearestSide)
            {
                xSide = this.GetNearestXSide((IWidePrimitive)shpross.Primitives[0], true);
                ySide = this.GetNearestYSide((IWidePrimitive)shpross.Primitives[0], true);
            }
            else
                throw new Exception();

            xDimension = DimensionFactory.CreateShprossLineDimension(shpross, modelBoundingBox, xSide);
            yDimension = DimensionFactory.CreateShprossLineDimension(shpross, modelBoundingBox, ySide);
            if (xDimension != null)
            {
                if (xDimension.Primitive.Length > noizeValue)
                {
                        dimensions.Add(xDimension);
                        this.IncrementSizeCounter(xSide);
                }
            }
            if (yDimension != null)
            {
                if (yDimension.Primitive.Length > noizeValue)
                {
                        dimensions.Add(yDimension);
                        this.IncrementSizeCounter(ySide);
                }
            }

        }



        public virtual void Visit(Spacer spacer)
        {
            foreach (IComponent connectedComponent in spacer.ConnectedComponents)
            {
                if (connectedComponent is SpacerConnector)
                {
                    SpacerConnector spacerConnector = connectedComponent as SpacerConnector;
                    Dimension dimension = DimensionFactory.CreatePinHoleDimension(spacerConnector, spacerConnector.ConnectedSpacerElement, dimensionsPreferences.PinHolesDimensions.DimensionType, true);
                    if(dimension != null)
                        this.dimensions.Add(dimension);
                }
            }
        }
        public virtual void Visit(ShprossConnectorStub stub)
        {
        }

        public virtual void Visit(ShprossConnector shprossConnector)
        {
        }

        public virtual void Visit(SpacerElement spacerElement)
        {
            if (spacerElement.Height != 0)
                return;

            Dimension xDimension;
            Dimension yDimension;

            DimensionSide xSide;
            DimensionSide ySide;

            IWidePrimitive primitive = spacerElement.Primitives[0] as IWidePrimitive;

            xSide = this.GetXSideForSpacerElement(spacerElement);
            ySide = this.GetYSideForSpacerElement(spacerElement);

            xDimension = DimensionFactory.CreateSpacerElementLineDimension(spacerElement, modelBoundingBox, xSide, dimensionsPreferences.SpacerDimensions.DimensionType);
            yDimension = DimensionFactory.CreateSpacerElementLineDimension(spacerElement, modelBoundingBox, ySide, dimensionsPreferences.SpacerDimensions.DimensionType);

            if (xDimension.Primitive.Length > noizeValue)
            {
                dimensions.Add(xDimension);
                this.IncrementSizeCounter(xSide);
            }
            if (yDimension.Primitive.Length > noizeValue)
            {
                dimensions.Add(yDimension);
                this.IncrementSizeCounter(ySide);
            }
        }

        public virtual void Visit(SpacerConnector spacerConnector)
        {
        }

        public virtual void Visit(ModelCell modelCell)
        {
            modelCell.Model.AcceptVisitor(this);
        }

        public virtual void Visit(ModelContainer modelContainer)
        {
        }

        public virtual void Visit(StickConnector stickConnector)
        {
        }

        public virtual void Visit(SnapOnCap snapOnCap)
        {
        }

        #endregion

        protected DimensionSide GetAppropriateXSide()
        {
            if (upSizesCounter < downSizesCounter)
                return DimensionSide.Up;
            else
                return DimensionSide.Down;
        }

        protected void IncrementSizeCounter(DimensionSide side)
        {
            switch (side)
            {
                case DimensionSide.Down:
                    downSizesCounter++;
                    return;
                case DimensionSide.Left:
                    leftSizesCounter++;
                    return;
                case DimensionSide.Right:
                    rightSizesCounter++;
                    return;
                case DimensionSide.Up:
                    upSizesCounter++;
                    return;
            }
        }

        protected DimensionSide GetAppropriateYSide()
        {
            if (leftSizesCounter < rightSizesCounter)
                return DimensionSide.Left;
            else
                return DimensionSide.Right;
        }

        protected DimensionSide GetNearestXSide(IEndedPrimitive primitive, bool byBoundingBox)
        {
            RectangleF boundingBox = primitive.BoundingBox; // centerBorder.BoundingBox;

            ModelPoint startPoint;
            ModelPoint endPoint;
            if (byBoundingBox)
            {
                startPoint = new ModelPoint(boundingBox.X, boundingBox.Y);
                endPoint = new ModelPoint(boundingBox.X + boundingBox.Width, boundingBox.Y + boundingBox.Height);
            }
            else
            {
                startPoint = primitive.StartPoint;
                endPoint = primitive.EndPoint;
            }
            Line upSideLine = DimensionFactory.GetSideLine(modelBoundingBox, DimensionSide.Up);
            Line downSideLine = DimensionFactory.GetSideLine(modelBoundingBox, DimensionSide.Down);

            double startPointDistanceToUpLine = upSideLine.GetDistanceFromSegmentToPoint(startPoint);
            double endPointDistanceToUpLine = upSideLine.GetDistanceFromSegmentToPoint(endPoint);

            double startPointDistanceToDownLine = downSideLine.GetDistanceFromSegmentToPoint(startPoint);
            double endPointDistanceToDownLine = downSideLine.GetDistanceFromSegmentToPoint(endPoint);

            double upLineDistance = Math.Min(startPointDistanceToUpLine, endPointDistanceToUpLine);
            double downLineDistance = Math.Min(startPointDistanceToDownLine, endPointDistanceToDownLine);

            if (!SimpleEqualProvider.IsEqual(upLineDistance, downLineDistance, 0.0001) && upLineDistance < downLineDistance)
                return DimensionSide.Up;
            else if (!SimpleEqualProvider.IsEqual(upLineDistance, downLineDistance, 0.0001) && downLineDistance < upLineDistance)
                return DimensionSide.Down;
            else
            {
                if (primitive.StartPoint.X < primitive.EndPoint.X)
                    return DimensionSide.Down;
                else
                    return DimensionSide.Up;
            }
        }

        protected DimensionSide GetNearestYSide(IEndedPrimitive primitive, bool byPrimitiveBoundingBox)
        {
            RectangleF boundingBox = primitive.BoundingBox; // centerBorder.BoundingBox;


            ModelPoint startPoint;
            ModelPoint endPoint;

            if (byPrimitiveBoundingBox)
            {
                startPoint = new ModelPoint(boundingBox.X, boundingBox.Y);
                endPoint = new ModelPoint(boundingBox.X + boundingBox.Width, boundingBox.Y + boundingBox.Height);
            }
            else
            {
                startPoint = primitive.StartPoint;
                endPoint = primitive.EndPoint;
            }

            Line leftSideLine = DimensionFactory.GetSideLine(modelBoundingBox, DimensionSide.Left);
            Line rightSideLine = DimensionFactory.GetSideLine(modelBoundingBox, DimensionSide.Right);

            double startPointDistanceToLeftLine = leftSideLine.GetDistanceFromSegmentToPoint(startPoint);
            double endPointDistanceToLeftLine = leftSideLine.GetDistanceFromSegmentToPoint(endPoint);

            double startPointDistanceToRightLine = rightSideLine.GetDistanceFromSegmentToPoint(startPoint);
            double endPointDistanceToRightLine = rightSideLine.GetDistanceFromSegmentToPoint(endPoint);

            double leftLineDistance = Math.Min(startPointDistanceToLeftLine, endPointDistanceToLeftLine);
            double rightLineDistance = Math.Min(startPointDistanceToRightLine, endPointDistanceToRightLine);

            if (!SimpleEqualProvider.IsEqual(leftLineDistance, rightLineDistance, 0.0001) && leftLineDistance < rightLineDistance)
                return DimensionSide.Left;
            else if (!SimpleEqualProvider.IsEqual(leftLineDistance, rightLineDistance, 0.0001) && rightLineDistance < leftLineDistance)
                return DimensionSide.Right;
            else
            {
                if (primitive.StartPoint.Y < primitive.EndPoint.Y)
                    return DimensionSide.Right;
                else
                    return DimensionSide.Left;
            }


        }

        protected DimensionSide GetXSideForSpacerElement(SpacerElement spacerElement)
        {
            if (spacerElement.StartPoint.X < spacerElement.EndPoint.X)
                return DimensionSide.Down;
            else
                return DimensionSide.Up;
        }

        protected DimensionSide GetYSideForSpacerElement(SpacerElement spacerElement)
        {
            if (spacerElement.StartPoint.Y < spacerElement.EndPoint.Y)
                return DimensionSide.Right;
            else
                return DimensionSide.Left;
        }

        #region IDimensionProcessor Members


        public void AddCompute(Type typeOfComponent)
        {
            if (!this.allowedTypesToCompute.Contains(typeOfComponent))
                this.allowedTypesToCompute.Add(typeOfComponent);
        }

        public void RemoveCompute(Type typeOfComponent)
        {
            this.allowedTypesToCompute.Remove(typeOfComponent);
        }

        #endregion

        #region IDimensionProcessor Members


        public bool ContainsCompute(Type typeOfComponent)
        {
            return this.allowedTypesToCompute.Contains(typeOfComponent);
        }

        #endregion

        #region IDimensionProcessor Members


        public void SetCompute(bool compute, Type typeOfComponent)
        {
            if (compute == true)
            {
                if (!this.ContainsCompute(typeOfComponent))
                    this.AddCompute(typeOfComponent);
            }
            else
            {
                this.RemoveCompute(typeOfComponent);
            }
        }

        #endregion

        #region IDimensionProcessor Members

        //public void SetModelBoundingBox(RectangleF boundingBox)
        //{
        //    this.modelBoundingBox = boundingBox;
        //}

        //public void UpdateState(ModelCell modelCell)
        //{
        //    this.RecalculateOffsets(this.modelBoundingBox);
        //}

        #endregion

        #region IDimensionProcessor Members


        public RectangleF GetUnionBoundingBox(Type typeOfDimension)
        {
            RectangleF rectangle = modelBoundingBox;
            foreach (Dimension dimension in this.dimensions)
                if (dimension.GetType() == typeOfDimension)
                    rectangle = RectangleF.Union(dimension.Primitive.BoundingBox, rectangle);

            return rectangle;
        }

        public void RecalculateOffsetDimensions(Type typeOfDimension, RectangleF boundingBox)
        {
            this.offsetDimensions.Clear();
            foreach (Dimension dimension in this.dimensions)
            {
                RectangleF primitiveBoundingBox = dimension.Primitive.BoundingBox;
                if (/*dimension.GetType() == typeOfDimension && */ dimension.Primitive is Line && (primitiveBoundingBox.Width == 0 || primitiveBoundingBox.Height == 0))
                {
                    double offset = ModelCalculations.GetOffsetToNearestBoundingBoxSide(dimension.Primitive as Line, boundingBox);
                    Dimension clone = dimension.Clone() as Dimension;
                    clone.Primitive.MoveWithOffset(clone.Primitive.GetNormalLine(0.5).Rotate(new Angle(Math.PI)).Multiply(offset));
                    this.offsetDimensions.Add(clone);
                    //                    dimension.Primitive.MoveWithOffset(dimension.Primitive.GetNormalLine(0.5).Rotate(new Angle(Math.PI)).Multiply(offset));
                }
            }
        }

        #endregion


        #region IModelVisitor Members


        public void Visit(ShprossEndToEndConnector endToEndConnector)
        {
         //   throw new NotImplementedException();
        }

        #endregion

        #region IModelVisitor Members


        public void Visit(ShprossChain shprossChain)
        {
         //   throw new NotImplementedException();
        }

        #endregion

        #region IModelVisitor Members


        public void Visit(SpacerContainer spacerContainer)
        {
          //  throw new NotImplementedException();
        }

        #endregion

        #region IModelVisitor Members


        public void Visit(CrossConnector crossConnector)
        {
      //      throw new NotImplementedException();
        }

        #endregion
    }
}
